//#include <stdio.h>
//#include <stdlib.h>//exit
//#include <unistd.h>//fork
//#include <sys/types.h>//wait/waitpid
//#include <sys/wait.h>
//
//#define TASK_NUM 5
//
//typedef void (*task_t)();
//
//
//void download()
//{
//    printf("模拟正在运行的下载任务\n");
//}
//void writelog()
//{
//    printf("模拟正在运行的写日志任务\n");
//}
//void show()
//{
//    printf("模拟正在运行的展示信息任务\n");
//}
//
//
//void InitTasks(task_t tasks[],int num)
//{
//    for(int i = 0;i < num;i++)
//    {
//        tasks[i] = NULL;
//    }
//}
//
//int AddTask(task_t tasks[],task_t t)
//{
//    int i = 0;
//    for(;i < TASK_NUM;i++)
//    {
//        if(tasks[i] == NULL)//如果这个数组有空的位置，就把任务放进去
//        {
//            tasks[i] = t;
//            return 1;
//        }
//    }
//    return 0;
//}
//
//void executeTask(task_t tasks[],int num)
//{
//    for(int i = 0;i < num;i++)
//    {
//        if(tasks[i])//回调式执行任务
//           tasks[i]();
//    }
//}
//
//void Worker(int cnt)
//{
//    printf("我是子进程，pid: %d,cnt: %d\n",getpid(),cnt);
//}
//
//int main()
//{
//    task_t tasks[TASK_NUM];
//    InitTasks(tasks,TASK_NUM);
//    AddTask(tasks,download);
//    AddTask(tasks,writelog);
//    AddTask(tasks,show);
//
//    pid_t id = fork();
//    if(id == 0){
//        //child
//        int cnt = 5;
//        while(cnt)
//        {
//            Worker(cnt);
//            sleep(1);//
//            cnt--;
//        }
//
//        exit(0);
//    }
//
//    //father
//    while(1)//非阻塞轮询
//    {
//        int status = 0;
//        pid_t rid = waitpid(id,&status,WNOHANG);//WNOHANG可以让父进程在等待过程中去做其他事情，而不是一直阻塞等待
//        //如果第三个参数是0，那么父进程会一直等待，直到子进程退出
//        //非阻塞地等待可以让等待方在等待的过程中顺便做做自己的事情
//        if(rid > 0)
//        {
//            printf("等待子进程成功，子进程正常退出,exit code: %d, exit signal: %d\n",(status>>8)&0xFF,status&0x7F);
//            break;
//        }
//        else if(rid == 0)
//        {
//            printf("父进程等待成功，但是子进程还没退出，在等待过程中去做其他事情了\n");
//            executeTask(tasks,TASK_NUM);//也可以在内部进行自己移除/新增对应的任务
//        }
//        else//最经典的就是waitpid的第一个参数传错了，也就是说等待的子进程不是该父进程的子进程，那么直接报错，可以尝试给上述的id+1
//        {
//            printf("等待失败，无法获取子进程信息\n");
//            break;
//        }
//
//        sleep(1);
//    }
//    return 0;
//            
//
//}




#include <stdio.h>
#include <unistd.h>//execl
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
       printf("pid: %d,开始执行系统指令\n",getpid());
       sleep(1);

       //execl("/usr/bin/ls","ls","-a","-l",NULL);//以NULL结尾
       //execl("/usr/bin/top","top",NULL);//只是把原先在物理内存的程序替换为目标位置的文件（程序），进程还是那个进程，所以execl叫做程序替换
       //execl("/usr/bin/pwd","pwd",NULL);

       //execlp("ls","ls","-a","-l",NULL);//自动在环境变量PATH中找ls,第二个参数还是要写的

       //char* const argv[] = {
       //     "ls",
       //     "-a",
       //     "-l",
       //     NULL
       //};
       ////execv("/usr/bin/ls",argv);
       //execvp(argv[0],argv);//和execlp一样，p表示环境变量PATH，在环境变量中自动找argv[0]对应的指令 

       //execl("./mytest","mytest",NULL);//和上面的一样，第二个参数直接写指令名字。这里证明了可以将程序替换为自己写的而不仅仅是系统指令
       //execl("/usr/bin/bash","bash","test.sh",NULL);//怎么输入指令就怎么写函数的参数
       execl("/usr/bin/python3","python3","test.py",NULL);//可以看出脚本语言或者其他编程语言都可以程序替换，因为exec*是进程的程序替换
       //不管什么语言运行起来都会变成进程，那么就可以跨语言地程序替换（系统大于一切）

       printf("pid: %d,结束执行系统指令\n",getpid());//执行exec* 这样的函数成功了，就是程序替换成功了，后续的代码就被替换了
       //也就是说该进程后续的代码和数据都被覆盖掉了，所以也不会被执行。反之，程序替换失败了，就会产生返回值（成功无返回值）

       
       //int n = execl("usr/bin/lsssss","lssss",NULL);//故意写错，证明了程序替换失败会返回-1，并且继续执行后续的代码
       ////int  n = execl("usr/bin/ls","lssss",NULL);//第二个参数是写错的，但是仍然能正常运行，因为第一个参数已经指明了具体文件
       ////第二个参数的文件找不到，编译器就自动去第一个参数找，所以写错了还能够运行
       //printf("pid: %d,n = %d,结束执行系统指令\n",getpid(),n);//执行exec* 这样的函数成功了，就是程序替换成功了，后续的代码就被替换了
       
       exit(1);
    }
    else//通过获取进程id证明了程序替换之后确实还是那个进程
    {
       pid_t rid = waitpid(-1,NULL,0);//以阻塞的方式等待任意一个子进程
       if(rid > 0)
       {
          printf("等待成功，rid: %d\n",rid);
       }
    }
    //如果是单进程的程序，那么程序替换直接替换整个程序之后的代码和数据；如果是多进程的，那么子进程进行程序替换不会影响父进程
    //因为进程具有独立性，子进程发生程序替换会对数据段和代码段进行写时拷贝，也就是重新开辟一段空间来存放代码和数据
    //另外程序替换不创建子进程，其两要素是找到文件的位置（第一个参数），告诉exec*如何执行（后面的可变参数列表）

    return 0;

}
